home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d18 / kruse_11.arc / INDEXHAS.PAS < prev    next >
Pascal/Delphi Source File  |  1990-11-30  |  32KB  |  931 lines

  1.  
  2. {outline of declaration of subprograms:
  3.  
  4.  1.     program IndexText(InText, InIndex, NewIndex, OutIndex, HashFile,
  5.                           NewHashFile, input, output);     (main program)
  6.  2.         function Lt(u, v: word):  Boolean;
  7.  3.         procedure ReadWord(var f: text;  var w: word);
  8.  4.         procedure WriteWord(var f: text; w: word);
  9.  4a.        built in CPU time function   clock;
  10.  
  11.  5.         procedure SplitWords;                       (phase 1)
  12.  5a.            function FindFile(ch: char): filecode;
  13.  6.             function HashAddress(w: word):  hashentry;
  14.  7.             procedure Initialize;
  15.  8.             procedure GetWord;
  16.  8a.                procedure TellUserPage;
  17.  9.                 procedure GetChar(var ch: char);
  18. 10.                 procedure AddChar(ch: char);
  19. 11.             procedure Conclude;
  20.  
  21. procedure ClassifyWords;
  22.     procedure InitializeTable(RefTable: RefHashTable);
  23.         function HashAddress(x: reference): integer;
  24.         procedure Insert(x: reference; pos: integer; var RefTable: RefHashTable);
  25.     procedure Place(var F: fileref; RefTable: RefHashTable);
  26.         function Empty(L: list): Boolean;
  27.     procedure LinkEntries(RefTable: RefHashTable; var NewList: list);
  28.         procedure RemoveFirst(var p: pointer; L: list);
  29.         procedure SkipBlank(var F: text);
  30.         procedure ReadReference(var r: pointer; var F: text);
  31.         procedure WriteReference(p: pointer; var NewIndex, NewHashFile: text);
  32.         procedure GetWordType(p: pointer);
  33.         procedure Delete(var p: pointer);
  34.     procedure CompareAndMerge(NewList: list;var InIndex,NewIndex,NewHashFile: text);
  35.             procedure Merge(p, q: pointer; var r: pointer);
  36.             procedure Divide(var p, q:  pointer);
  37.             procedure MergeSort(var p: pointer);
  38.         procedure MainMergeSort(var L: list);  
  39. }
  40.  
  41.  
  42. program IndexText(InText, InIndex, NewIndex, HashFile, NewHashFile,
  43.                   input, output);
  44.  
  45. {Produces word counts and list of references for the document file 
  46.  InText. Uses the master word list in file InIndex, if provided. Output word
  47.  list for new text goes to file NewIndex. HashFile contains the common words
  48.  to be ignored. If not specified, it is created on output, containing the
  49.  words so flagged by the user.}
  50. {This implementation uses only phases 1 and 2. A smaller array of text files
  51.  is also used, as specified in the exercise section.}
  52.  
  53. const
  54.   maxwd         =   20;             {More letters in word will be ignored.}
  55.   minwd         =    1;                    {Shorter words will be ignored.}
  56.   hashsize      = 2003;                                 {should be a prime}
  57.   linesperpage  =   66;                {assumes standard spacing and paper}
  58.   maxheight     =   20;               {for building binary tree in phase 2}
  59.   A             =  'A';
  60.   Z             =  'Z';
  61.   hyphen        =  '-';
  62.   blank         =  ' ';
  63.   apostrophe    = '''';               {requires two `'s  to represent one}
  64.   underscore    =  '_';
  65.   ordbackspace  =    8;            {ASCII control character for backspace}
  66.   ordformfeed   =   12;             {ASCII control character for new page}
  67.   changecase    =   32;    {ASCII difference between upper and lower case}
  68.   nfiles        =    8;  {number of temporary files for unprocessed words}
  69.   MaxRowLength  =   130;                 {maximum length of output records}
  70.  
  71. type
  72.   word          =  packed array[1..maxwd] of char;
  73.   reference     =  record
  74.                       wd:   word;
  75.                       pg:   integer;               {count or page number}
  76.                    end;
  77.   fileref       =  file of reference;              {used for local files}
  78.   letter        =  A..Z;
  79.   hashentry     =  1..hashsize;
  80.   filecode      =  1..nfiles;
  81.  
  82. var
  83.   InText,                                     {document being processed}
  84.   InIndex,                                            {master word list}
  85.   NewIndex,                              {word list of current document}
  86.   HashFile,
  87.   NewHashFile:      text;
  88.   RefFile:      array[filecode] of fileref; {local files used for auxilary
  89.                                storage of words from phase 1 to phase 2:
  90.                 Normally, a separate file exist for each initial letter,
  91.         this version uses nfiles files due operating system constraints.}
  92.   blankword:    word;                           {will contain all blanks}
  93.  
  94. {The next two variables were originally declared in procedure SplitWords,
  95.  they have been moved to this level in order to access them globally.}
  96.   outcount:     array[filecode] of integer;    {counters for word  files}
  97.   wordcount:    integer;                 {count of all words in the text}
  98.  
  99.   intextname,
  100.   inlistname,
  101.   newlistname,
  102.   newhashname:  word;                    {used to get filename from user}
  103.   lastletter:   array[filecode] of letter;     {last letter in each file}
  104.   PresentTime,
  105.   StartTime:    integer;                         {used to track CPU time}
  106.   RowLength:    integer;   {ensures records will not exceed MaxRowLength}
  107.  
  108.  
  109.  
  110. function Lt( u, v: word): Boolean;
  111. {Determains if word u precedes word v lexicographically.}
  112. begin
  113.   Lt := (u < v)
  114. end;
  115.  
  116. procedure ReadWord( var F: text;  var w: word);
  117. {Reads word w from text file F.  Assumes not at end of file.}
  118. {Uses packed array, replace using a loop if your system does not 
  119.  support packed arrays. }
  120. begin                           {procedure ReadWord}
  121.   read(F, w)
  122. end;                            {procedure ReadWord}
  123.  
  124. procedure WriteWord( var F: text; w: word);
  125. {Writes word w to text file F}
  126. {Uses packed array, replace using a loop if your system does not 
  127.  support packed arrays. }
  128. begin                           {procedure WriteWord}
  129.   write(F, w)
  130. end;                            {procedure WriteWord}
  131.  
  132. procedure SetTimer;     {Call once at beginning of program execution.}
  133. {Finds the CPU time when called, and keeps in variables for reference.}
  134. {System dependent procedure.}
  135. begin
  136.   PresentTime := clock;
  137.   StartTime := PresentTime;
  138. end;
  139.  
  140. function TotalTime:  real;
  141. {Returns the total CPU time, in seconds, since call to SetTimer.}
  142. {System dependent procedure.}
  143. begin
  144.   TotalTime := (clock - StartTime) / 1000.0;
  145. end;
  146.  
  147. function ElapsedTime:  real;
  148. {Returns elapsed CPU time since last call to function ElapsedTime,
  149.  or call to SetTimer, whichever is more recent.}
  150. {System dependent procedure.}
  151. var r: integer;
  152. begin
  153.   r := clock;
  154.   ElapsedTime := (r - PresentTime) / 1000.0;
  155.   PresentTime := r;
  156. end;
  157.  
  158.  
  159.  
  160. procedure SplitWords;
  161. {sets up hash table, reads text, and divides into nfiles word lists}
  162.  
  163. var
  164.   hash:       array[hashentry] of reference;              {hash table}
  165.   pagecount:  integer;                 {keeps the current page number}
  166.   addpage:    integer;       {amount to increase pagecount after word}
  167.   linecount:  integer;                     {lines on the current page}
  168.   w:          word;                   {word currently being processed}
  169.   x:          hashentry;             {location of w, if in hash table}
  170.   endinput:   Boolean;   {true if and only if input has all been read}
  171.   code:       filecode;                {into which file does word go?}
  172.  
  173. {The following variables are kept for use in procedure GetWord, and for
  174.  efficiency are set up only once in procedure Initialize:}
  175.   backspace,
  176.   formfeed:   char;
  177.   alphabet,                           {letters only - to start a word}
  178.   contchar:   set of char;     {other characters ok in middle of word}
  179.  
  180.  
  181.   function  FindFile( ch:  letter):  filecode;
  182.   {Uses binary decision tree to select one of nfiles = 8 files depending
  183.    on the letter ch.  These letters must be the same as those in the
  184.    global array  lastletter  .}
  185.  
  186.   begin                           {function FindFile}
  187.     if            ch < 'M' then
  188.       if          ch < 'E' then
  189.         if        ch < 'C' then  FindFile := 1
  190.                            else  FindFile := 2
  191.       else if     ch < 'H' then  FindFile := 3
  192.                            else  FindFile := 4
  193.     else if       ch < 'S' then
  194.       if          ch < 'P' then  FindFile := 5
  195.                            else  FindFile := 6
  196.       else if     ch < 'T' then  FindFile := 7
  197.                            else  FindFile := 8
  198.   end;                            {function FindFile}
  199.  
  200.  
  201.  
  202.   function HashAddress(w: word): hashentry;
  203.   {calculates the location in hash table of word w, or, if not there,
  204.    returns pointing to the blank word where w should go}
  205.  
  206.   var
  207.     x,                            {calculated location}
  208.     inc:     integer;             {increment for open addressing}
  209.   begin                           {function HashAddress}
  210.     x := abs(ord(w[1])*ord(w[2])+ord(w[4])+ord(w[6])) mod hashsize + 1;
  211. {Hash function assumes long word length. For short word machines
  212.  we must ensure that the result is non-negative, and worry about overflow.}
  213.  
  214.     if (hash[x].wd <> w) and (hash[x].wd <> blankword) then
  215.       begin
  216.         inc   := (abs(ord(w[3])-95) mod 29);
  217.                   {A key dependent increment is used to avoid clustering.}
  218.         repeat
  219.           inc := inc + 1;
  220.           if inc > hashsize then
  221.             writeln(w,' causes hash table to become full, infinite loop.');
  222.           x := x + inc;
  223.           if x > hashsize then x := x - hashsize;
  224.         until (w =  hash[x].wd)  or  (blankword = hash[x].wd)
  225.       end;
  226.     HashAddress := x
  227.   end;                            {function HashAddress}
  228.  
  229.  
  230.   procedure Initialize;
  231.   {sets up constant-valued sets for use in GetWord. Opens the text file
  232.    and initializes various counters. Opens file holding hash table (if any),
  233.    and reads or otherwise initializes table}
  234.   var
  235.     i:         integer;          {general purpose loop control}
  236.  
  237.   begin                           {procedure Initialize}
  238.     backspace:= chr(ordbackspace);
  239.     formfeed := chr(ordformfeed); {initialize ASCII control characters}
  240.     alphabet := ['A'..'Z', 'a'..'z'];      {letters only, to start a word}
  241.     contchar := [hyphen, apostrophe, backspace, underscore];
  242.                                 {characters which will not terminate word}
  243.     for i := 1 to maxwd do
  244.       blankword[i] := blank;
  245.  
  246.     write('Name of input text file?');
  247.     ReadWord(input, intextname); readln;
  248.     open(InText, intextname, readonly);
  249.     reset(InText);
  250.     endinput := eof(InText);
  251.  
  252.     repeat
  253.       write( 'What is the page number on which the text begins?');
  254.       readln(pagecount);
  255.       if pagecount < 0 then
  256.         writeln('Must be a non-negative integer.')
  257.     until pagecount >= 0;
  258.     linecount := 0;
  259.     addpage   := 0;
  260.     wordcount := 0;
  261.  
  262.     for i := 1 to nfiles do
  263.     begin
  264.       rewrite( RefFile[i] );
  265.       outcount[i] := 0
  266.     end;
  267.     lastletter[1] := 'B';
  268.     lastletter[2] := 'D';
  269.     lastletter[3] := 'G';
  270.     lastletter[4] := 'L';
  271.     lastletter[5] := 'O';
  272.     lastletter[6] := 'R';
  273.     lastletter[7] := 'S';
  274.     lastletter[8] := 'Z';
  275.  
  276.     reset(HashFile);   {assumes HASHFILE.DAT is in current directory}
  277.  
  278.     for i := 1 to hashsize do
  279.     with hash[i] do 
  280.       begin
  281.         read(HashFile, pg);
  282.         get(HashFile);         {skip the blank between number and word}
  283.         ReadWord(HashFile, wd);
  284.         readln(HashFile);
  285.         pg := 0;                     {initialize all the counts to 0}
  286.       end;
  287.     writeln('The hash table has been read.')
  288.   end;                                        {procedure Initialize}
  289.  
  290.  
  291.  
  292.   procedure GetWord( var  w: word);
  293.   {Gets words from input file InText, and returns only words
  294.    at least minwd characters long.  Parameter endinput becomes
  295.    true if and only if the end of InText is reached with no word to return.
  296.    the procedure also updates global variables wordcount and linecount,
  297.    updates the global variable pagecount after each linesperpage cr's,
  298.    or after each formfeed, whichever comes first, and
  299.    uses the sets alphabet and contchar and various character constants.}
  300.  
  301.   label 1;           {used by GetChar to exit procedure upon eof(InText)}
  302.  
  303.   var  c:      0..maxwd;                    {count of characters in word}
  304.        ch:     char;                      {character currently processed}
  305.        endln:  Boolean;                           {at the end of a line?}
  306.  
  307.  
  308.   procedure TellUserPage;         {keep the user informed of progress}
  309.   var   i: integer;
  310.   begin
  311.     i := pagecount + addpage;
  312.     writeln('At page', i:4, ' word count is', wordcount:7)
  313.   end;
  314.  
  315.  
  316.   procedure GetChar(var ch: char);
  317.   {gets a character from input text into ch; checks for eof; updates
  318.    page count and line count}
  319.  
  320.   begin                                                {procedure GetChar}
  321.     if eof(InText) then
  322.       if c >= minwd then
  323.         ch := '.'              {special character to end the current word}
  324.       else begin                         {no word to return; set endinput}
  325.         endinput := true;
  326.         goto 1                                        {exit from GetWord.}
  327.       end
  328.     else begin                   {not end of file: process next character}
  329.       while InText^ in [underscore, backspace] do
  330.         get( InText);
  331.       ch := InText^;
  332.       endln := eoln(InText);
  333.       get(InText);
  334.       if endln then
  335.       begin
  336.         linecount := linecount + 1;
  337.         if linecount >= linesperpage then
  338.           begin
  339.             addpage := addpage + 1;
  340.             linecount := 0;
  341.             TellUserPage
  342.           end
  343.       end;
  344.       if ch = formfeed then
  345.         begin
  346.           addpage := addpage + 1;
  347.           linecount := 0;
  348.           TellUserPage;
  349.           endln := true;            {Treat formfeed like end of line.}
  350.           ch := blank
  351.         end
  352.     end
  353.   end;                                            {procedure GetChar}
  354.  
  355.  
  356.   procedure AddChar(ch: char);
  357.   {adds given character to word, if possible}
  358.   begin                           {procedure AddChar}
  359.     if c < maxwd then
  360.     begin
  361.       c := c + 1;
  362.       w[c] := ch
  363.     end
  364.   end;                            {procedure AddChar}
  365.  
  366.  
  367.   begin                           {procedure GetWord}
  368.     repeat                {until current word is at least minwd chars long}
  369.       c := 0;
  370.       repeat
  371.         GetChar(ch)               {Find a letter which will start the word.}
  372.       until ch in alphabet;
  373.       pagecount := pagecount + addpage;
  374.       addpage := 0;
  375.       if ch in ['a'..'z'] then       {translate first letter to upper case.}
  376.         ch := chr(ord(ch) - changecase); {assumes ASCII ordering of letters}
  377.       AddChar(ch);                          {put first letter into the word}
  378.       GetChar(ch);
  379.       while (ch in alphabet) or (ch in contchar) do
  380.         if ch in alphabet then                {add letters directly to word}
  381.         begin                                            {processing letter}
  382.           AddChar(ch);
  383.           GetChar(ch)
  384.         end                                              {processing letter}
  385.         else if ch = hyphen then
  386.         begin                                            {processing hyphen}
  387.           GetChar(ch);                       {Find what comes after hyphen.}
  388.           if endln then
  389.             while ch = ' ' do
  390.               GetChar(ch)       {Delete both the hyphen and the end of line}
  391.           else if ch = hyphen then      {Two hyphens form a dash; ends word}
  392.             ch := blank                 {Use a blank to terminate the word.}
  393.           else if ch in alphabet then
  394.             AddChar(hyphen)                  {Include other hyphens in word}
  395.           else      {nothing}
  396.         end                                              {processing hyphen}
  397.         else if ch = apostrophe then
  398.         begin                                        {processing apostrophe}
  399.           GetChar(ch);
  400.           if ch = 's' then              {Delete  `'s'   at end of word only}
  401.           begin
  402.             GetChar(ch);
  403.             if ch in contchar then
  404.             begin
  405.               AddChar(apostrophe);
  406.               AddChar('s')
  407.             end
  408.           end
  409.           else if ch in alphabet then
  410.              AddChar(apostrophe)                      {Allow contractions.}
  411.         end                                         {processing apostrophe}
  412.         else         {Remaining possibilities are backspace and underscore.}
  413.           GetChar(ch);                           {Delete these characters.}
  414.       {While loop on continuing characters ends here.}
  415.       wordcount := wordcount + 1
  416.     until c >= minwd;                              {Skip over short words.}
  417.  
  418.     while c < maxwd do                                  {Fill with blanks.}
  419.     begin
  420.       c := c + 1;
  421.       w[c] := blank
  422.     end;
  423.   1:      {When end of file occurs, program will exit to here from GetChar}
  424.   end;                                                  {procedure GetWord}
  425.  
  426.  
  427.  
  428. procedure Conclude;
  429. {Writes out counts of various word lists. For some systems, it is 
  430.  necessary to close files, which should be done here.}
  431.  
  432. var
  433.   i,j:        integer;                                {loop index}
  434.   response:   char;                    {user's answer to question}
  435.  
  436. begin                           {procedure Conclude}
  437.   writeln('The total number of words read in is ', wordcount:7);
  438.   writeln;
  439.   writeln('The number of words to process further in the next stage,');
  440.   writeln('in each temporary file, is below.');
  441.   writeln('     a-b     c-d     e-g     h-l     m-o     p-r      s      t-z');
  442.   for i := 1 to nfiles do
  443.     write(outcount[i]:8);
  444.   writeln;
  445.   writeln;
  446.  
  447. (*                    not implemented:
  448.   repeat
  449.     write('Do you wish the counts from hash table to be kept in a file (y,n)?');
  450.     readln(response);
  451.     if response > 'Z' then response := chr(ord(response)-changecase)
  452.   until response in ['N', 'Y'];
  453.   if response = 'Y' then
  454.   begin
  455.  
  456.     write('Name of file ?');
  457.     ReadWord(input, newhashname);
  458.     readln;
  459.     open(NewHashFile, newhashname);
  460.     rewrite(NewHashFile);
  461.  
  462.     for i := 1 to hashsize do
  463.     with hash[i] do begin
  464.       write(NewHashFile, pg:4, ' ');
  465.       j := 1;
  466.       repeat
  467.         write(NewHashFile, wd[j]);
  468.         j := j + 1;
  469.       until (wd[j] = ' ') or (j >= maxwd);
  470.       writeln(NewHashFile)
  471.     end
  472.   end                 *)
  473. end;                            {procedure Conclude}
  474.  
  475.  
  476. begin                                          {procedure  SplitWords}
  477.   Initialize;                   {sets up files, hash table, constants}
  478.   GetWord(w);                       {obtain a single word from InText}
  479.   while not endinput do
  480.   begin
  481.     x := HashAddress(w);
  482.     if w = hash[x].wd then
  483.       hash[x].pg := hash[x].pg + 1
  484.     else begin                  {not in hash table; put into RefFile}
  485.       code := FindFile( w[1] );
  486.       outcount[code] := outcount[code] + 1;
  487.       with RefFile[code]^ do
  488.       begin
  489.         wd := w;
  490.         pg := pagecount
  491.       end;
  492.       Put(RefFile[code])
  493.     end;
  494.     GetWord(w)
  495.   end;
  496.   Conclude                           {writes word counts to output.}
  497. end;                                          {procedure SplitWords}
  498.  
  499.  
  500.  
  501.  
  502. {start of phase 2}
  503.  
  504. procedure ClassifyWords;
  505. {The references stored in the temporary files are placed in a new hash table,
  506.  the words from the file InIndex are compared with the words in the new table
  507.  as they are merged into the file NewIndex.}
  508.  
  509. const
  510.   RefTableSize = 3023;   {Size of the hash table to temporarily store words.}
  511.   RefTableMax = 3022;
  512. type
  513.   wordtype  = (hash, count, index);          {ways to process a word}
  514.   pointref  = ^reflist;
  515.   reflist   = record                            {list of references}
  516.                 pg:   integer;
  517.                 next: pointref
  518.               end;
  519.   pointer   = ^node;
  520.   node      = record                     {node of list storing wrods.}
  521.                 wd:       word;
  522.                 kind:     wordtype;
  523.                 ct:       integer;
  524.                 ref:      pointref;
  525.                 next:     pointer
  526.               end;
  527.        {Cannot use varying types as @wordtype is not known upon first reading.}
  528.   list = record
  529.            head:  pointer
  530.          end;
  531.   RefHashTable = array[0..RefTableMax] of list;
  532.  
  533. var
  534.   code:       filecode;          {loop through temporary files}
  535.   RefTable:   RefHashTable;      {stores all references in memory}
  536.   NewList:    list;
  537.  
  538. function Empty(L: list): Boolean;
  539. begin
  540.   Empty := (L.head = nil)
  541. end;
  542.  
  543. procedure InitializeTable(var RefTable: RefHashTable);
  544. var
  545.   i:  integer;
  546. begin                           {procedure InitializeTable}
  547.   for i := 0 to RefTableMax do
  548.     RefTable[i].head := nil;
  549. end;                            {procedure InitializeTable}
  550.  
  551.  
  552. function RefTableAddress(x: reference): integer;
  553. { Returns hashed address of reference. }
  554. var
  555.   i:  integer;
  556.   h:  integer;
  557. begin                                 {function Hash}
  558.   h := 0;
  559.   with x do
  560.     for i := 1 to maxwd do
  561.       h := h + ord(wd[i]);
  562.   RefTableAddress := h mod RefTableSize
  563. end;                                  {function Hash}
  564.  
  565.  
  566. procedure Insert(x: reference; pos: integer; var RefTable: RefHashTable);
  567. { Inserts the reference into the hash table of references. }
  568. var
  569.   done:  Boolean;
  570.   p:  pointer;
  571.   q:  pointref;
  572. begin                                 {procedure Insert}
  573.   done := false;
  574.   p := RefTable[pos].head;
  575.   while (p <> nil) and (not done) do
  576.   begin              {Search for the word, update the reference if it is found.}
  577.     if p^.wd = x.wd then
  578.     begin
  579.       p^.ct := p^.ct + 1;                  {update count and page reference}
  580.       new(q);
  581.       q^.pg := x.pg;
  582.       q^.next := p^.ref;
  583.       p^.ref := q;
  584.       done := true
  585.     end
  586.     else
  587.       p := p^.next
  588.   end;
  589.   if not done then
  590.   begin            {Insert a new entry if the word is not already in the table.}
  591.     p := nil;
  592.     new(p);
  593.     p^.wd := x.wd;
  594.     p^.ct := 1;                 {Initialize the count and the page refernces.}
  595.     new(q);
  596.     q^.pg := x.pg;
  597.     q^.next := nil;
  598.     p^.ref := q;
  599.     p^.next := RefTable[pos].head;
  600.     RefTable[pos].head := p
  601.   end
  602. end;                                  {procedure Insert}
  603.  
  604.  
  605. procedure Place(var F: fileref; var RefTable: RefHashTable);
  606. { Places the words in file @F into the reference table. }
  607. var
  608.   x:  reference;
  609. begin                            {procedure Place}
  610.   reset(F);
  611.   while not eof(F) do
  612.   begin
  613.     x := F^;
  614.     get(F);
  615.     Insert(x, RefTableAddress(x), RefTable)
  616.   end
  617. end;                             {procedure Place}
  618.  
  619.  
  620. procedure LinkEntries(var RefTable: RefHashTable;  var NewList: list);
  621. { The references in the hashed table are combined into the list @NewList. }
  622. var
  623.   i: integer;
  624.   p: pointer;
  625. begin                            {procedure LinkEntries}
  626.   i := 0;
  627.   while (i < RefTableMax) and Empty(RefTable[i]) do  {find the first entry}
  628.     i := i + 1;
  629.   if i <= RefTableMax then
  630.   begin
  631.     NewList.head := RefTable[i].head; {Initialize the list to point to the first entry.}
  632.     p := RefTable[i].head;
  633.     if p <> nil then                 {Find the end of the first chain.}
  634.       while p^.next <> nil do
  635.         p := p^.next;
  636.     while (i < RefTableMax) do            {link remaining entries}
  637.     begin
  638.       i := i + 1;
  639.       if not Empty(RefTable[i]) then
  640.       begin
  641.         p^.next := RefTable[i].head;
  642.         while p^.next <> nil do    {Move @p to the end of the chain.}
  643.           p := p^.next
  644.       end
  645.     end
  646.   end
  647.   else
  648.     NewList.head := nil
  649. end;                             {procedure LinkEntries}
  650.  
  651.  
  652. procedure RemoveFirst(var p: pointer; var L: list);
  653. { Removes the first node from the list @L. }
  654. begin
  655.   p := L.head;
  656.   if not Empty(L) then
  657.   begin
  658.     L.head := L.head^.next;
  659.     p^.next := nil
  660.   end
  661. end;
  662.  
  663.  
  664. procedure ReadReference(var r: pointer; var F: text);
  665. { Reads refernce from the file @F.  }
  666. var
  667.   k:  char;
  668. begin                                  {procedure ReadReference}
  669.   if eof(F) then
  670.     r := nil
  671.   else begin
  672.     ReadWord(F, r^.wd);
  673.     readln(F, k);
  674.     case  k  of
  675.       'F', 'f':  r^.kind := hash;
  676.       'C', 'c':  begin
  677.                    r^.kind := count;
  678.                    r^.ct := 0
  679.                  end;
  680.       'I', 'i':  begin
  681.                    r^.kind := index;
  682.                    r^.ref := nil
  683.                  end
  684.     end
  685.   end
  686. end;                                   {procedure ReadReference}
  687.  
  688.  
  689. procedure WriteReference(p: pointer;  var NewIndex, NewHashFile: text);
  690. var
  691.   q:  pointref;
  692. begin                                 {procedure WriteReference}
  693.   with p^ do
  694.     case kind of
  695.       hash: begin
  696.               WriteWord(NewHashFile, wd);
  697.               writeln(NewHashFile)
  698.             end;
  699.       count:begin
  700.               WriteWord(NewIndex, wd);
  701.               write(NewIndex, 'c');
  702.               writeln(NewIndex, ct:5)
  703.             end;
  704.       index:begin
  705.               WriteWord(NewIndex, wd);
  706.               write(NewIndex, 'i');
  707.               q := ref;
  708.               while q <> nil do
  709.               begin
  710.                 write(NewIndex, q^.pg:5);
  711.                 q := q^.next
  712.               end;
  713.               writeln(NewIndex)
  714.             end
  715.     end
  716. end;                                  {procedure WriteReference}
  717.  
  718.  
  719. procedure GetWordType(p: pointer);
  720. { Request the user to specify the category of the given word. }
  721. var
  722.   response: char;
  723. begin                       {procedure GetWordType}
  724.   with p^ do
  725.   begin
  726.     repeat
  727.       WriteWord(output, wd);
  728.       write(' is (F, C, I)?');
  729.       readln(response)
  730.     until response in ['F', 'f', 'C', 'c', 'I', 'i'];
  731.     case response of
  732.       'F', 'f': kind := hash;
  733.       'C', 'c': kind := count;
  734.       'I', 'i': kind := index
  735.     end
  736.   end
  737. end;                        {procedure GetWordType}
  738.  
  739.  
  740. procedure Delete(var p: pointer);
  741. { Delete the word @p^ as well as all of the page references associated with it. }
  742. var
  743.   q, r:  pointref;
  744. begin                                 {procedure Delete}
  745.   if p^.kind = index then
  746.   begin
  747.     q := p^.ref;
  748.     while q <> nil do
  749.     begin                   {dispose the reference list}
  750.       r := q^.next;
  751.       dispose(q);
  752.       p^.ref := r;
  753.       q := r
  754.     end
  755.   end;
  756.   dispose(p)            {dispose the node itself}
  757. end;                                  {procedure Delete}
  758.  
  759.  
  760. procedure CompareAndMerge(var L: list; var InIndex, NewIndex, NewHashFile: text);
  761. { Compare the list @L with @InIndex, merge if was found. }
  762. var
  763.   p, r:  pointer;
  764. begin                              {procedure CompareAndMerge}
  765.   RemoveFirst(p, L);
  766.   new(r);
  767.   ReadReference(r, InIndex);
  768.   while p <> nil do
  769.     if r = nil then
  770.     begin
  771.       GetWordType(p);
  772.       WriteReference(p, NewIndex, NewHashFile);
  773.       Delete(p);               {Remove reference list and node from memory.}
  774.       RemoveFirst(p, L)
  775.     end
  776.     else if p^.wd < r^.wd then
  777.     begin
  778.       GetWordType(p);
  779.       WriteReference(p, NewIndex, NewHashFile);
  780.       Delete(p);               {Remove reference list and node from memory.}
  781.       RemoveFirst(p, L)
  782.     end
  783.     else if p^.wd > r^.wd then     {do not write word not used to NewIndex}
  784.       ReadReference(r, InIndex)
  785.     else begin {p^.wd = r^.wd}
  786.       p^.kind := r^.kind;
  787.       WriteReference(p, NewIndex, NewHashFile);
  788.       Delete(p);
  789.       RemoveFirst(p, L);
  790.       ReadReference(r, InIndex)
  791.     end
  792. end;                               {procedure CompareAndMerge}
  793.  
  794.  
  795. procedure Merge(p, q: pointer; var r: pointer);
  796. {Merges two sorted lists into one, that will begin at r;
  797.  requires that both lists be non empty.  This version is modified 
  798.  slightly from the version listed in the text due to a difference 
  799.  in the data structures used.}
  800. var
  801.   s:  pointer;     {always points to last node of sorted list}
  802. begin                                  {procedure Merge}
  803.   if (p = nil) or (q = nil) then
  804.     writeln('Merge called with empty list(s).');
  805.   {First find the head, r, of the merged list.}
  806.   if p^.wd <= q^.wd then                   {change .info.key to .wd}
  807.   begin
  808.     r := p;
  809.     p := p^.next
  810.   end
  811.   else begin
  812.     r := q;
  813.     q := q^.next
  814.   end;
  815.   s := r;       {s always points to the last entry of the merged list.}
  816.   while (p <> nil) and (q <> nil) do
  817.     if p^.wd <= q^.wd then                   {change .info.key to .wd}
  818.     begin
  819.       s^.next := p; {Attach the node with the smaller key to the sorted list.}
  820.       s := p;
  821.       p := p^.next      {Advance to the next unmerged node.}
  822.     end
  823.     else begin
  824.       s^.next := q;
  825.       s := q;
  826.       q := q^.next
  827.     end;
  828.   {After one list is exhausted, attach the remainder of the other one.}
  829.   if p = nil then
  830.     s^.next := q
  831.   else
  832.     s^.next := p
  833. end;                                     {procedure Merge}
  834.  
  835. (*===========================================================================*)
  836. procedure Divide(var p, q:  pointer);
  837. {takes the list to which p points, divides it in half, and returns with
  838.  p pointing to head of the first half and q to the head of second half;
  839.  requires that the original list contain at least two items, or an 
  840.  error occurs}
  841. var
  842.   r:  pointer;
  843. begin                                      {procedure Divide}
  844.   q := p;                 {Start q at position 1, and r at position 3.}
  845.   r := p^.next;
  846.   r := r^.next;
  847.   while r <> nil do       {Move r two positions for each move of q.}
  848.   begin
  849.     r := r^.next;
  850.     q := q^.next;
  851.     if r <> nil then
  852.       r := r^.next
  853.   end;
  854.   {Break the list into halves after q^.}
  855.   r := q^.next;
  856.   q^.next := nil;
  857.   q := r
  858. end;                                    {procedure Divide}
  859.  
  860. procedure MainMergeSort(var L: list);
  861. {Main procedure to invoke recursive MergeSort}
  862.  
  863. procedure MergeSort(var p: pointer);
  864. {divides the list starting at p^ in half, sorts it recursively, and merges
  865.  the sublists}
  866. var
  867.   q:  pointer;          {marks the halfway point in the list}
  868. begin
  869.   if p <> nil then if p^.next <> nil then
  870.   begin   {Otherwise, list has 0 or 1 entry, with no need to sort.}
  871.     Divide(p, q);
  872.     MergeSort(p);
  873.     MergeSort(q);
  874.     Merge(p, q, p)
  875.   End
  876. End;
  877.  
  878. begin
  879.   MergeSort(L.head)
  880. end;
  881. (*===========================================================================*)
  882.  
  883. begin                            {procedure ClassifyWords}
  884.  
  885.   write('Name of input word list ?');
  886.   ReadWord(input, inlistname);
  887.   readln;
  888.   open(InIndex, inlistname, readonly);   {may vary on different systems}
  889.   reset(InIndex);
  890.  
  891.   write('Name of output word list ?');
  892.   ReadWord(input, newlistname);
  893.   readln;
  894.   open(NewIndex, newlistname);         {may vary on different systems}
  895.   rewrite(NewIndex);
  896.  
  897.   write('Name of file for new hash words ?');
  898.   ReadWord(input, newhashname);
  899.   readln;
  900.   open(NewHashFile, newhashname);         {may vary on different systems}
  901.   rewrite(NewHashFile);
  902.  
  903.   InitializeTable(RefTable);
  904.   for code := 1 to nfiles do
  905.     Place(RefFile[code], RefTable);
  906.   LinkEntries(RefTable, NewList);
  907.   MainMergeSort(NewList);
  908.   if not Empty(NewList) then
  909.     CompareAndMerge(NewList, InIndex, NewIndex, NewHashFile);
  910.   close(InIndex);         {may vary on different systems}
  911.   close(NewIndex);
  912.   close(NewHashFile)
  913. end;                             {procedure ClassifyWords}
  914.  
  915.  
  916.  
  917. begin                                                    {main program}
  918.   SetTimer;
  919.   SplitWords;                                                 {Phase 1}
  920.   writeln('Time in first phase is ', ElapsedTime:7:1, '   seconds.');
  921.   writeln;
  922.  
  923.   ClassifyWords;                                              {Phase 2}
  924.   writeln('Time in second phase is', ElapsedTime:7:1, '  seconds.');
  925.  
  926.   writeln;
  927.   writeln('Processing of input document ', intextname, '  is complete.');
  928.   writeln('Total time in program was ', TotalTime:7:1, '   seconds.')
  929. end.
  930.  
  931.